package com.icehand.payment.pay.wechat;


import com.fasterxml.jackson.core.JsonProcessingException;;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.icehand.common.enums.SignTypeEnum;
import com.icehand.common.utils.MD5Utils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;


/**
 * @author icehand
 **/
public class WeChatPaymentSignatureUtils {
	private static final String KEY_SIGN = "sign";

	/**
	 * 生成带有 sign 的 XML 格式字符串
	 *
	 * @param data Map类型数据
	 * @param key API密钥
	 * @return 含有sign字段的XML
	 */
	public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
		return generateSignedXml(data, key, SignTypeEnum.MD5);
	}

	/**
	 * 生成带有 sign 的 XML 格式字符串
	 *
	 * @param data Map类型数据
	 * @param key API密钥
	 * @param signType 签名类型
	 * @return 含有sign字段的XML
	 */
	public static String generateSignedXml(final Map<String, String> data, String key, SignTypeEnum signType) throws JsonProcessingException {
		String sign = generateSignature(data, key, signType);
		data.put(KEY_SIGN, sign);
		XmlMapper xmlMapper = new XmlMapper();
		return xmlMapper.writeValueAsString(data);
	}


	/**
	 * 判断签名是否正确
	 *
	 * @param xmlStr XML格式数据
	 * @param key API密钥
	 * @return 签名是否正确
	 * @throws Exception
	 */
	public static boolean isSignatureValid(String xmlStr, String key) throws IOException {
		XmlMapper xmlMapper = new XmlMapper();
		Map<String, String> data = xmlMapper.readValue(xmlStr,new TypeReference<Map<String, String>>(){});
		if (null == data || !data.containsKey(KEY_SIGN) ) {
			return false;
		}
		String sign = data.get(KEY_SIGN);
		return generateSignature(data, key).equals(sign);
	}

	/**
	 * 判断签名是否正确，必须包含sign字段，否则返回false。使用MD5签名。
	 *
	 * @param data Map类型数据
	 * @param key API密钥
	 * @return 签名是否正确
	 * @throws Exception
	 */
	public static boolean isSignatureValid(Map<String, String> data, String key) {
		return isSignatureValid(data, key, SignTypeEnum.MD5);
	}

	/**
	 * 判断签名是否正确，必须包含sign字段，否则返回false。
	 *
	 * @param data Map类型数据
	 * @param key API密钥
	 * @param signType 签名方式
	 * @return 签名是否正确
	 * @throws Exception
	 */
	public static boolean isSignatureValid(Map<String, String> data, String key, SignTypeEnum signType) {
		if (!data.containsKey(KEY_SIGN) ) {
			return false;
		}
		String sign = data.get(KEY_SIGN);
		return Objects.equals(generateSignature(data, key, signType), sign);
	}

	/**
	 * 生成签名
	 *
	 * @param data 待签名数据
	 * @param key API密钥
	 * @return 签名
	 */
	public static String generateSignature(final Map<String, String> data, String key) {
		return generateSignature(data, key, SignTypeEnum.MD5);
	}

	/**
	 * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
	 *
	 * @param data 待签名数据
	 * @param key API密钥
	 * @param signType 签名方式
	 * @return 签名
	 */
	public static String generateSignature(final Map<String, String> data, String key, SignTypeEnum signType){
		Set<String> keySet = data.keySet();
		String[] keyArray = keySet.toArray(new String[0]);
		Arrays.sort(keyArray);
		StringBuilder sb = new StringBuilder();
		for (String k : keyArray) {
			if (k.equals(KEY_SIGN)) {
				continue;
			}
			if (data.get(k).trim().length() > 0){
				sb.append(k).append("=").append(data.get(k).trim()).append("&");
			}
		}
		sb.append("key=").append(key);
		if (SignTypeEnum.MD5.equals(signType)) {
			return MD5Utils.MD5Encode(sb.toString()).toUpperCase();
		}else if (SignTypeEnum.HMACSHA256.equals(signType)) {
			return HMACSHA256(sb.toString(), key);
		}

		return null;
	}


	/**
	 * 获取随机字符串 Nonce Str
	 * @return String 随机字符串
	 */
	public static String generateNonceStr() {
		return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
	}

	/**
	 * 生成 HMACSHA256
	 * @param data 待处理数据
	 * @param key 密钥
	 * @return 加密结果
	 * @throws Exception
	 */
	public static String HMACSHA256(String data, String key) {
		byte[] array=null;
		try {
			Mac sha256Hmac = Mac.getInstance("HmacSHA256");
			SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
			sha256Hmac.init(secretKey);
			array = sha256Hmac.doFinal(data.getBytes(StandardCharsets.UTF_8));
		} catch (NoSuchAlgorithmException | InvalidKeyException e) {
			e.printStackTrace();
		}

		StringBuilder sb = new StringBuilder();
		if(array == null) {
			return null;
		}

		for (byte item : array) {
			sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
		}
		return sb.toString().toUpperCase();
	}


	/**
	 * 微信支付SHA1签名
	 */
	public static String signatureSHA1(Map<String, String> map) {
		Set<String> keySet = map.keySet();
		String[] str = new String[map.size()];
		StringBuilder tmp = new StringBuilder();
		// 进行字典排序
		str = keySet.toArray(str);
		Arrays.sort(str);
		int i;
		for (i = 0; i < str.length; i++) {
			String t = str[i] + "=" + map.get(str[i]) + "&";
			tmp.append(t);
		}

		String tend = tmp.toString().substring(0, tmp.length() - 1);
		MessageDigest md = null;
		byte[] bytes = null;
		try {

			md = MessageDigest.getInstance("SHA-1");
			bytes = md.digest(tend.getBytes(StandardCharsets.UTF_8));
		} catch (Exception e) {
			e.printStackTrace();
		}

		assert bytes != null;
		String singe = byteToStr(bytes);
		return singe.toLowerCase();

	}

	public static String sha1Check(String[] str) {

		StringBuilder tmp = new StringBuilder();
		Arrays.sort(str);
		for (String t : str) {
			tmp.append(t);
		}
		String tend = tmp.toString();
		MessageDigest md;
		byte[] bytes = null;
		try {

			md = MessageDigest.getInstance("SHA-1");
			bytes = md.digest(tend.getBytes(StandardCharsets.UTF_8));
		} catch (Exception e) {
			e.printStackTrace();
		}

		assert bytes != null;
		String singe = byteToStr(bytes);
		return singe.toUpperCase();
	}

	/**
	 * 字节数组转换为字符串
	 *
	 * @param byteArray
	 * @return
	 */
	public static String byteToStr(byte[] byteArray) {
		StringBuilder strDigest = new StringBuilder();
		for (byte aByteArray : byteArray) {
			strDigest.append(byteToHexStr(aByteArray));
		}
		return strDigest.toString();
	}

	/**
	 * 字节转换为字符串
	 *
	 * @param mByte
	 * @return
	 */
	public static String byteToHexStr(byte mByte) {
		char[] digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
				'B', 'C', 'D', 'E', 'F' };
		char[] tempArr = new char[2];
		tempArr[0] = digit[(mByte >>> 4) & 0X0F];
		tempArr[1] = digit[mByte & 0X0F];

		return new String(tempArr);
	}
}
